<?php
/*
 * http访问通用接口
*/
define('HTTP_ERROR_PATH', ERROR_ROOT_PATH);	//HTTP报错日志目录
define('HTTP_RUNLOG_PATH', ERROR_ROOT_PATH);	//HTTP执行日志目录

class Http
{
	private $uri;	//请求地址
	private $post = '';	//POST内容
	private $ch;	//连接资源
	public $user_agent = '';	//在HTTP请求中包含一个”user-agent”头的字符串
	public $content_type = '';	//内容类型，常用的4种POST提交数据方式：application/x-www-form-urlencoded, multipart/form-data, application/json, text/xml
	public $charset = 'UTF-8';	//字符集编码
	public $headers = array();	//HTTP头信息
	public $custom_request = '';	//当进行HTTP请求时，传递一个字符被GET或HEAD使用
	public $run_log = 0;	//是否记录执行日志
	public $longrun_log = 1;	//是否记录长时间访问日志
	public $longrun_min_duration = 3;	//长时间访问日志的最小时长秒数阀值，不低于此值的记入长时间访问日志
	public $lastrun_start_time;	//最后一次访问开始时间
	public $lastrun_end_time;	//最后一次访问结束时间
	public $lastrun_duration;	//最后一次访问时长
	public $info = array();	//最后一次访问的状态信息
	public $fail_response_body = '';	//最后一次访问失败时的响应信息
	public $timeout = 5;	//等待返回值超时秒数限制
	public $connect_timeout = 5;	//连接超时秒数限制
	public $errno;	//最后一次访问的报错码
	public $error;	//最后一次访问的报错信息

	public function __construct()
	{
		$this->run_log = IS_TEST;	//测试环境默认记录http执行日志
	}

	//HTTP请求，$uri为要访问的http地址（含GET模式的传参），$post为POST模式专用的传参数组（key为参数名，value为参数值）或者json串等字符串，$post为空则是GET模式，$fail_return_false 为请求失败时是否仅返回false，返回值为http接口返回结果，false为访问失败
	public function request($uri, $post = array(), $fail_return_false = true)
	{
		$this->uri = $uri;
		if (!defined('CURL_HTTP_VERSION_2_0'))
		{
			define('CURL_HTTP_VERSION_2_0', 3);	//支持http 2.0
		}
		$this->ch = curl_init();
		curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);	//支持http 2.0
		curl_setopt($this->ch, CURLOPT_URL, $this->uri);
		if ($post)
		{
			$this->post = is_array($post) ? http_build_query($post) : $post;	//兼容数组和字符串
			curl_setopt($this->ch, CURLOPT_POST, 1);
			curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->post);
			$this->headers['Content-Length'] = 'Content-Length:' . strlen($this->post);
		}
		if ($this->content_type)
		{
			$header_content_type = 'Content-Type:' . $this->content_type;
			if ($this->charset)
			{
				$header_content_type .= ';charset=' . $this->charset;
			}
			$this->headers['Content-Type'] = $header_content_type;
		}
		if ($this->headers)
		{
			curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->headers);
		}
		if ($this->custom_request)
		{
			curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $this->custom_request);
		}
		if (stripos($uri, 'https://') === 0)
		{
			curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); // 跳过证书检查
			curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 2);  // 从证书中检查SSL加密算法是否存在
		}
		curl_setopt($this->ch, CURLOPT_HEADER, 0);
		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
		//curl_setopt($this->ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);	//默认IPv4，PHP 5.3以上才可用
		curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout);
		curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout);
		if ($this->user_agent)
		{
			curl_setopt($this->ch, CURLOPT_USERAGENT, $this->user_agent);
		}
		$this->lastrun_start_time = microtime(true);
		$result = curl_exec($this->ch);
		$this->lastrun_end_time = microtime(true);
		$this->lastrun_duration = $this->lastrun_end_time - $this->lastrun_start_time;
		$this->info = curl_getinfo($this->ch);
		if (curl_errno($this->ch) || $this->info['http_code'] >= 400 || $result == 'FAIL' || $result == 'Fail' || $result == 'false')
		{
			$this->fail_response_body = $result;
			@$this->error('Failed to request');
			if ($fail_return_false)
			{
				$result = false;
			}
		}
		else
		{
			if ($this->longrun_log && $this->lastrun_duration >= $this->longrun_min_duration)
			{
				@$this->save_longrun_log('Request: ' . $result);
			}
			if ($this->run_log)
			{
				@$this->save_log('Request: ' . $result);
			}
		}
		curl_close($this->ch);
		return $result;
	}

	//HTTP请求仅获取header，$uri为要访问的http地址，$fail_return_false 为请求失败时是否仅返回false，返回值为curl -I http接口返回结果，false为访问失败
	public function header($uri, $fail_return_false = true)
	{
		$this->uri = $uri;
		if (!defined('CURL_HTTP_VERSION_2_0'))
		{
			define('CURL_HTTP_VERSION_2_0', 3);	//支持http 2.0
		}
		$this->ch = curl_init();
		curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);	//支持http 2.0
		curl_setopt($this->ch, CURLOPT_URL, $this->uri);
		if (stripos($uri, 'https://') === 0)
		{
			curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, 0); // 跳过证书检查
			curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, 2);  // 从证书中检查SSL加密算法是否存在
		}
		curl_setopt($this->ch, CURLOPT_HEADER, 1);
		curl_setopt($this->ch, CURLOPT_NOBODY, 1);
		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1);
		//curl_setopt($this->ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);	//默认IPv4，PHP 5.3以上才可用
		curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout);
		curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout);
		if ($this->content_type)
		{
			$header_content_type = 'Content-Type:' . $this->content_type;
			if ($this->charset)
			{
				$header_content_type .= ';charset=' . $this->charset;
			}
			$this->headers['Content-Type'] = $header_content_type;
		}
		if ($this->headers)
		{
			curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->headers);
		}
		if ($this->user_agent)
		{
			curl_setopt($this->ch, CURLOPT_USERAGENT, $this->user_agent);
		}
		$this->lastrun_start_time = microtime(true);
		$result = curl_exec($this->ch);
		$this->lastrun_end_time = microtime(true);
		$this->lastrun_duration = $this->lastrun_end_time - $this->lastrun_start_time;
		$this->info = curl_getinfo($this->ch);
		if (curl_errno($this->ch))
		{
			$this->fail_response_body = $result;
			@$this->error('Failed to get header');
			if ($fail_return_false)
			{
				$result = false;
			}
		}
		else
		{
			if ($this->longrun_log && $this->lastrun_duration >= $this->longrun_min_duration)
			{
				@$this->save_longrun_log('Header: ' . $result);
			}
			if ($this->run_log)
			{
				@$this->save_log('Header: ' . $result);
			}
		}
		curl_close($this->ch);
		return $result;
	}

	//解析header信息，$input 字符串  返回 数组
    public function header_string_to_array($input)
    {
		if ($input !== false)
		{
			$header = array();
			$headerPart = explode("\r\n", $input);
			list ($httpProtocol, $statusCode, $statStr) = explode(' ', array_shift($headerPart), 3);
			foreach ($headerPart as $item) 
			{
				if ($item)
				{
					$pos = strpos($item, ':');
					$key = substr($item, 0, $pos);
					$value = substr($item, $pos+1);
					$header[$key] = trim($value);
				}
			}
		}
		else
		{
			$header = false;
		}
		return $header;
	}

	//记录报错日志
	private function error($msg = '')
	{
		$time = time();
		$this->error = is_resource($this->ch) ? curl_error($this->ch) : 'N/A';
		$this->errno = is_resource($this->ch) ? curl_errno($this->ch) : 'N/A';
		$log = 'Time: ' . date('Y-m-d H:i:s', $time) . '; ';
		$log .= 'Duration: ' . $this->lastrun_duration . ' s, ' . date('Y-m-d H:i:s', $this->lastrun_start_time) . ' - ' . date('H:i:s', $this->lastrun_end_time) . '; ';
		$log .= 'API Message(128): ' . substr($msg, 0, 128) . '; ';
		$log .= 'Error Number: ' . $this->errno . '; ';
		$log .= 'Error Message: ' . $this->error . '; ';
		$log .= 'From URL/Path: ' . $_SERVER['PHP_SELF'] . '; ';
		$log .= 'Request: ' . $this->uri . '; ';
		$log .= 'Post(256): ' . substr($this->post, 0, 256) . '; ';
		$log .= 'User Agent: ' . $this->user_agent . '; ';
		$cost = array();
		$cost['namelookup'] = $this->info['namelookup_time'];
		$cost['namelookup_to_connect'] = $this->info['connect_time'] - $this->info['namelookup_time'];
		$cost['connect_to_pretrasfer'] = $this->info['pretransfer_time'] - $this->info['connect_time'];
		$cost['pretrasfer_to_starttransfer'] = $this->info['starttransfer_time'] - $this->info['pretransfer_time'];
		$cost['starttransfer_to_end'] = $this->info['total_time'] - $this->info['starttransfer_time'];
		$log_timecost = 'Time Cost: (';
		foreach ($cost as $k => $v)
		{
			$log_timecost .= $k . ': ' . $v . ', ';
		}
		$log .= substr($log_timecost, 0, -2) . '); ';
		$log .= 'Headers: ' . var_export($this->headers, true) . '; ';
		$log .= 'Info: ' . var_export($this->info, true) . '; ';
		$log .= "\n\n";
		is_dir(HTTP_ERROR_PATH) or mkdir(HTTP_ERROR_PATH);
		$log_file = HTTP_ERROR_PATH . 'http_error_' . date('Y-m-d', $time) . '.log';
		if ($fp = fopen($log_file, 'a'))
		{
			if (strlen($log) <= 4096)	//不超4K不需加flock
			{
				$result = fwrite($fp, $log);
				fclose($fp);
			}
			else
			{
				flock($fp, LOCK_EX);
				$result = fwrite($fp, $log);
				flock($fp, LOCK_UN);
				fclose($fp);
			}
		}
		return $result;
	}

	//记录执行日志
	private function save_log($msg = '')
	{
		$time = time();
		$backtrace_arr = debug_backtrace();
		$backtrace = var_export($backtrace_arr, true);
		$log = 'Time: ' . date('Y-m-d H:i:s', $time) . '; ';
		$log .= 'Duration: ' . $this->lastrun_duration . ' s, ' . date('Y-m-d H:i:s', $this->lastrun_start_time) . ' - ' . date('H:i:s', $this->lastrun_end_time) . '; ';
		$log .= 'From URL/Path: ' . $_SERVER['PHP_SELF'] . '; ';
		$log .= 'Request: ' . $this->uri . '; ';
		$log .= 'Result: ' . $msg . '; ';
		$log .= 'Post: ' . $this->post . '; ';
		$log .= 'User Agent: ' . $this->user_agent . '; ';
		$cost = array();
		$cost['namelookup'] = $this->info['namelookup_time'];
		$cost['namelookup_to_connect'] = $this->info['connect_time'] - $this->info['namelookup_time'];
		$cost['connect_to_pretrasfer'] = $this->info['pretransfer_time'] - $this->info['connect_time'];
		$cost['pretrasfer_to_starttransfer'] = $this->info['starttransfer_time'] - $this->info['pretransfer_time'];
		$cost['starttransfer_to_end'] = $this->info['total_time'] - $this->info['starttransfer_time'];
		$log_timecost = 'Time Cost: (';
		foreach ($cost as $k => $v)
		{
			$log_timecost .= $k . ': ' . $v . ", ";
		}
		$log .= substr($log_timecost, 0, -2) . '); ';
		$log .= 'Headers: ' . var_export($this->headers, true) . '; ';
		$log .= 'Info: ' . var_export($this->info, true) . '; ';
		$log .= 'Backtrace: ' . $backtrace . '; ';
		$log .= "\n\n";
	
		is_dir(HTTP_RUNLOG_PATH) or mkdir(HTTP_RUNLOG_PATH);
		$log_file = HTTP_RUNLOG_PATH . 'http_run_' . date('Y-m-d', $time) . '.log';
		if ($fp = fopen($log_file, 'a'))
		{
			if (strlen($log) <= 4096)	//不超4K不需加flock
			{
				$result = fwrite($fp, $log);
				fclose($fp);
			}
			else
			{
				flock($fp, LOCK_EX);
				$result = fwrite($fp, $log);
				flock($fp, LOCK_UN);
				fclose($fp);
			}
		}
		return $result;
	}

	//记录长时间访问日志
	private function save_longrun_log($msg = '')
	{
		$time = time();
		$log = 'Time: ' . date('Y-m-d H:i:s', $time) . '; ';
		$log .= 'Duration: ' . $this->lastrun_duration . ' s, ' . date('Y-m-d H:i:s', $this->lastrun_start_time) . ' - ' . date('H:i:s', $this->lastrun_end_time) . '; ';
		$log .= 'From URL/Path: ' . $_SERVER['PHP_SELF'] . '; ';
		$log .= 'Request: ' . $this->uri . '; ';
		$log .= 'Result(128): ' . substr($msg, 0, 128) . '; ';
		$log .= 'Post(256): ' . substr($this->post, 0, 256) . '; ';
		$log .= 'User Agent: ' . $this->user_agent . '; ';
		$cost = array();
		$cost['namelookup'] = $this->info['namelookup_time'];
		$cost['namelookup_to_connect'] = $this->info['connect_time'] - $this->info['namelookup_time'];
		$cost['connect_to_pretrasfer'] = $this->info['pretransfer_time'] - $this->info['connect_time'];
		$cost['pretrasfer_to_starttransfer'] = $this->info['starttransfer_time'] - $this->info['pretransfer_time'];
		$cost['starttransfer_to_end'] = $this->info['total_time'] - $this->info['starttransfer_time'];
		$log_timecost = 'Time Cost: (';
		foreach ($cost as $k => $v)
		{
			$log_timecost .= $k . ': ' . $v . ", ";
		}
		$log .= substr($log_timecost, 0, -2) . '); ';
		$log .= 'Headers: ' . var_export($this->headers, true) . '; ';
		$log .= 'Info: ' . var_export($this->info, true) . '; ';
		$log .= "\n\n";
		is_dir(HTTP_RUNLOG_PATH) or mkdir(HTTP_RUNLOG_PATH);
		$log_file = HTTP_RUNLOG_PATH . 'http_longrun_' . date('Y-m-d', $time) . '.log';
		if ($fp = fopen($log_file, 'a'))
		{
			if (strlen($log) <= 4096)	//不超4K不需加flock
			{
				$result = fwrite($fp, $log);
				fclose($fp);
			}
			else
			{
				flock($fp, LOCK_EX);
				$result = fwrite($fp, $log);
				flock($fp, LOCK_UN);
				fclose($fp);
			}
		}
		return $result;
	}
}
